`timescale 1ns / 1ps

module im2col
#(
    parameter N_IO      = 16,
    parameter N_CH      = 1280,
    parameter BIT       = 8,
    parameter COL       = 40,
    parameter ROW       = 20,
    parameter PADVAL    = 0,
    parameter RAM_TYPE  = "block",
    parameter RAM_LATENCY = 2
)
(
    input   clk,
    input   rst,
    
    input   i_vld,
    output  i_rdy,
    input   [N_IO * BIT -1 : 0]    i_data,
    
    output  o_vld,
    input   o_rdy,
    output  [N_IO * BIT -1 : 0]    o_data
);

localparam FOLD = N_CH / N_IO;
localparam W_COL = $clog2(COL);
localparam W_FOD = $clog2(FOLD);

localparam DEPTH = 3 * COL * N_CH / N_IO;
localparam ADDR_WIDTH = $clog2(DEPTH);
localparam DATA_WIDTH = N_IO * BIT;

wire w_en;
wire [ADDR_WIDTH-1 : 0] w_addr;
wire [DATA_WIDTH-1 : 0] w_data;
wire r_en;
wire [ADDR_WIDTH-1 : 0] r_addr;
wire [DATA_WIDTH-1 : 0] r_data;

wire m_rdy;
wire m_vld;
wire m_ld_pad;
wire [      1 : 0] m_row;
wire [W_COL-1 : 0] m_col;
wire [W_FOD-1 : 0] m_fod;

reg  [5 : 0] buffer_state;
wire [5 : 0] buffer_clr;
wire [5 : 0] buffer_set;

always @(posedge clk)
begin
    if (rst)
        buffer_state <= 6'b110000;
    else
        buffer_state <= buffer_state & ~buffer_clr | buffer_set;
end

im2col_writer #(
    .DEPTH      (DEPTH),
    .D_WIDTH    (DATA_WIDTH),
    .A_WIDTH    (ADDR_WIDTH)
) inst_writer
(
    .clk            (clk),
    .rst            (rst),
    
    .i_vld          (i_vld),
    .i_rdy          (i_rdy),
    .i_data         (i_data),

    .bstate         (buffer_state),
    .bstate_set     (buffer_set),
    
    .w_en           (w_en),
    .w_addr         (w_addr),
    .w_data         (w_data)
);

im2col_addr_gen #(
    .FOLD           (FOLD),
    .COL            (COL),
    .ROW            (ROW)
) inst_addr_gen
(
    .clk            (clk),
    .rst            (rst),
    
    .o_vld          (m_vld),
    .o_rdy          (m_rdy),
    .o_ld_pad       (m_ld_pad),
    .o_row          (m_row),
    .o_col          (m_col),
    .o_fod          (m_fod),

    .bstate         (buffer_state),
    .bstate_clr     (buffer_clr)
);

im2col_reader #(
    .N_IO           ( N_IO          ),
    .BIT            ( BIT           ),
    .FOLD           ( FOLD          ),
    .COL            ( COL           ),
    .PADVAL         ( PADVAL        ),
    .A_WIDTH        ( ADDR_WIDTH    ),
    .RAM_LATENCY    ( RAM_LATENCY   )
) inst_reader
(
    .clk            (clk),
    .rst            (rst),
    
    .i_vld          (m_vld),
    .i_rdy          (m_rdy),
    .i_ld_pad       (m_ld_pad),
    .i_row          (m_row),
    .i_col          (m_col),
    .i_fod          (m_fod),

    .o_vld          (o_vld),
    .o_rdy          (o_rdy),
    .o_data         (o_data),
    
    .r_en           (r_en),
    .r_addr         (r_addr),
    .r_data         (r_data)
);

zq_sdpram #(
    .ADDR_WIDTH ( ADDR_WIDTH  ),
    .DATA_WIDTH ( DATA_WIDTH  ),
    .DEPTH      (      DEPTH  ),
    .LATENCY    ( RAM_LATENCY ),
    .RAMTYPE    ( RAM_TYPE    )
)
inst_sdpram (
    .clk                     ( clk       ),

    .ena_w                   ( w_en      ),
    .wea                     ( 1'b1      ),
    .addr_w                  ( w_addr    ),
    .din                     ( w_data    ),

    .ena_r                   ( r_en      ),
    .addr_r                  ( r_addr    ),
    .dout                    ( r_data    )
);

endmodule


module im2col_writer
#(
    parameter DEPTH     = 9600,
    parameter D_WIDTH   = 16 * 8,
    parameter A_WIDTH   = 14
)
(
    input   clk,
    input   rst,
    
    input   i_vld,
    output  i_rdy,
    input   [D_WIDTH-1 : 0]     i_data,

    input   [5 : 0]             bstate,
    output  [5 : 0]             bstate_set,
    
    output  w_en,
    output  [A_WIDTH-1 : 0]     w_addr,
    output  [D_WIDTH-1 : 0]     w_data
);

localparam BLOCK = DEPTH / 6;

wire                 addr_ena;
wire [A_WIDTH-1 : 0] addr;
wire [A_WIDTH-1 : 0] addr_flag [5 : 0];
wire [5 : 0]         addr_last;

wire         cblk_ena;
wire [2 : 0] cblk;

assign i_rdy = ~bstate[cblk];
assign w_en   = addr_ena;
assign w_addr = addr;
assign w_data = i_data;

assign addr_ena = ~bstate[cblk] & i_vld;
assign cblk_ena = addr_ena & (|addr_last);
assign bstate_set = {6{addr_ena}} & addr_last;

genvar i;
generate
    for (i = 0; i < 6; i = i + 1)
    begin
        // assign addr_flag[i] = BLOCK * i + BLOCK - (i % 2);   // safety
        assign addr_flag[i] = BLOCK * i + BLOCK - 1;     // data hazard
        assign addr_last[i] = (addr == addr_flag[i]);
    end
endgenerate

zq_counter #(
    .N  (DEPTH)
) inst_cnt_addr
(
    .clk    (clk),
    .rst    (rst),
    .clken  (addr_ena),
    .last   (),
    .out    (addr)
);

zq_counter #(
    .N  (6)
) inst_cnt_blk
(
    .clk    (clk),
    .rst    (rst),
    .clken  (cblk_ena),
    .last   (),
    .out    (cblk)
);

endmodule


module im2col_addr_gen #(
    parameter FOLD      = 80,
    parameter COL       = 40,
    parameter ROW       = 20
)
(
    input   clk,
    input   rst,
    
    input   o_rdy,
    output  o_vld,
    output                       o_ld_pad,  // 0: access memory; 1: padding
    output               [1 : 0] o_row,
    output  [$clog2( COL)-1 : 0] o_col,
    output  [$clog2(FOLD)-1 : 0] o_fod,

    input   [5 : 0]             bstate,
    output  [5 : 0]             bstate_clr
);

localparam W_ROW = $clog2(ROW);
localparam W_COL = $clog2(COL);
localparam W_FOD = $clog2(FOLD);
localparam COL_MID  = COL / 2;

localparam S0 = 9'b000000001;
localparam S1 = 9'b000000010;
localparam S2 = 9'b000000100;
localparam S3 = 9'b000001000;
localparam S4 = 9'b000010000;
localparam S5 = 9'b000100000;
localparam S6 = 9'b001000000;
localparam S7 = 9'b010000000;
localparam S8 = 9'b100000000;

reg  [1 : 0]        idxa;
reg  [1 : 0]        idxb;
reg  [1 : 0]        idxc;

reg  [8 : 0]        cstate;
reg  [8 : 0]        nstate;
reg  [2 : 0]        cblk;
wire                cblk_lo;
wire                enable;

reg  [1 : 0]        oo_row;
reg  [W_COL-1 : 0]  oo_col;
reg                 ld_pad;
reg  [5 : 0]        bs_clr;

wire [W_ROW-1 : 0]  row;
wire                row_ena;
wire                row_zero;
wire                row_last;
wire [W_COL-1 : 0]  col;
wire                col_ena;
wire                col_zero;
wire                col_last;
wire [W_FOD-1 : 0]  fod;
wire                fod_ena;
wire                fod_zero;
wire                fod_last;

assign o_vld = bstate[cblk] | ld_pad;
assign o_row = oo_row;
assign o_col = oo_col;
assign o_fod = fod;
assign o_ld_pad = ld_pad;
assign bstate_clr = bs_clr;

assign enable = (bstate[cblk] | ld_pad) & o_rdy;
assign cblk_lo = col >= COL_MID;

assign row_ena = cstate[8] & col_last & fod_last & enable;
assign col_ena = cstate[8] & fod_last & enable;
assign fod_ena = cstate[8] & enable;
assign row_zero = ~|row;
assign col_zero = ~|col;
assign fod_zero = ~|fod;

always @(posedge clk)
begin
    if (rst) begin
        idxa <= 2'b10;
        idxb <= 2'b00;
        idxc <= 2'b01;
    end
    else begin
        if (row_ena) begin
            idxa <= idxb;
            idxb <= idxc;
            idxc <= idxa;
        end
    end
end

always @(posedge clk)
begin
    if (rst)
        cstate <= S0;
    else
        cstate <= nstate;
end

always @(*)
begin
    (* full_case *)
    case (cstate)
		S0: nstate = enable ? S1 : S0;
		S1: nstate = enable ? S2 : S1;
        S2: nstate = enable ? S3 : S2;
		S3: nstate = enable ? S4 : S3;
        S4: nstate = enable ? S5 : S4;
		S5: nstate = enable ? S6 : S5;
        S6: nstate = enable ? S7 : S6;
		S7: nstate = enable ? S8 : S7;
        S8: nstate = enable ? S0 : S8;
		default:
			nstate = S0;
	endcase
end

always @(*)
begin
    (* full_case *)
    case (cstate)
		S0:
        begin
            cblk = { idxa, cblk_lo };
            oo_row = idxa;
            oo_col = col - 1'b1;
            ld_pad = row_zero | col_zero;
            bs_clr = 6'b000000;
            // bs_clr[{ idxa, 1'b0 }] = (col == COL_MID) & fod_last & enable;   // safety
		end
		S1:
        begin
            cblk = { idxa, cblk_lo };
            oo_row = idxa;
            oo_col = col;
            ld_pad = row_zero;
            bs_clr = 6'b000000;
            bs_clr[{ idxa, 1'b1 }] = col_last & fod_last & enable;
		end
        S2:
        begin
            cblk = { idxa, cblk_lo };
            oo_row = idxa;
            oo_col = col + 1'b1;
            ld_pad = row_zero | col_last;
            bs_clr = 6'b0;
        end
        S3:
        begin
            cblk = { idxb, cblk_lo };
            oo_row = idxb;
            oo_col = col - 1'b1;
            ld_pad = col_zero;
            bs_clr = 6'b0;
        end
        S4:
        begin
            cblk = { idxb, cblk_lo };
            oo_row = idxb;
            oo_col = col;
            ld_pad = 1'b0;
            bs_clr = 6'b0;
        end
        S5:
        begin
            cblk = { idxb, cblk_lo };
            oo_row = idxb;
            oo_col = col + 1'b1;
            ld_pad = col_last;
            bs_clr = 6'b0;
        end
        S6:
        begin
            cblk = { idxc, cblk_lo };
            oo_row = idxc;
            oo_col = col - 1'b1;
            ld_pad = row_last | col_zero;
            bs_clr = 6'b0;
        end
        S7:
        begin
            cblk = { idxc, cblk_lo };
            oo_row = idxc;
            oo_col = col;
            ld_pad = row_last;
            bs_clr = 6'b0;
        end
        S8:
        begin
            cblk = { idxc, cblk_lo };
            oo_row = idxc;
            oo_col = col + 1'b1;
            ld_pad = row_last | col_last;
            bs_clr = 6'b000000;
            bs_clr[{ idxa, 1'b0 }] = (col == COL_MID - 1'b1) & fod_last & enable;   // data hazard
        end
	endcase
end

zq_counter #(
    .N  (FOLD)
) inst_cnt_fod
(
    .clk    (clk),
    .rst    (rst),
    .clken  (fod_ena),
    .last   (fod_last),
    .out    (fod)
);

zq_counter #(
    .N  (COL)
) inst_cnt_col
(
    .clk    (clk),
    .rst    (rst),
    .clken  (col_ena),
    .last   (col_last),
    .out    (col)
);

zq_counter #(
    .N  (ROW)
) inst_cnt_row
(
    .clk    (clk),
    .rst    (rst),
    .clken  (row_ena),
    .last   (row_last),
    .out    (row)
);

endmodule


module im2col_reader
#(
    parameter N_IO      = 16,
    parameter BIT       = 8,
    parameter FOLD      = 80,
    parameter COL       = 40,
    parameter PADVAL    = 0,
    parameter A_WIDTH   = 14,
    parameter RAM_LATENCY = 2
)
(
    input   clk,
    input   rst,
    
    output  i_rdy,
    input   i_vld,
    input                        i_ld_pad,  // 0: access memory; 1: padding
    input                [1 : 0] i_row,
    input   [$clog2( COL)-1 : 0] i_col,
    input   [$clog2(FOLD)-1 : 0] i_fod,

    input   o_rdy,
    output  o_vld,
    output  [N_IO*BIT -1 : 0]    o_data,
    
    output  r_en,
    output  [A_WIDTH  -1 : 0]    r_addr,
    input   [N_IO*BIT -1 : 0]    r_data
);

localparam PADVEC = { N_IO{ PADVAL[BIT-1 : 0] } };

wire s_ld_pad;
wire enable;
reg  [A_WIDTH-1 : 0]    addr;

assign enable = ~o_vld | o_rdy;

assign i_rdy = enable;
assign r_en  = enable;
assign r_addr = addr;
assign o_data = s_ld_pad ? PADVEC : r_data;

always @(posedge clk)
begin
    if (enable & ~i_ld_pad & i_vld)
        addr <= i_fod + FOLD * i_col + COL * FOLD * i_row;
end

shift_reg_rst #(
    .N     ( RAM_LATENCY + 1 ),
    .WIDTH (               2 )
)
inst_ctrl_chain (
    .clk                     ( clk      ),
    .rst                     ( rst      ),

    .clken                   ( enable   ),
    .i_data                  ({ i_vld, i_ld_pad }),
    .o_data                  ({ o_vld, s_ld_pad })
);

endmodule
